package com.fxl.majiang.room;

import com.fxl.majiang.enums.*;
import com.fxl.majiang.exception.CustomException;
import lombok.Data;
import lombok.SneakyThrows;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author fengxinglei
 * @date 2022/8/3 11:29
 * @description 玩家数据
 */

@Data
public class Player {

    private String userId;

    /**
     * 座次
     */
    private SeatEnum seat;

    // 当前状态
    private PlayerStatusEnum playStatus;

    // 用户当前拥有的牌
    private List<TilesEnum> ownedTiles = new ArrayList<>();

    // 用户吃、碰、杠等牌组
    private List<TilesGroup> ownedTilesGroup = new ArrayList<>();

    // 用户花牌牌组
    private List<TilesGroup> ownedHuaTilesGroup = new ArrayList<>();

    // 用户打出的牌
    private List<TilesEnum> playedTiles = new ArrayList<>();

    public Player(String userId, SeatEnum seat) {
        this.userId = userId;
        this.seat = seat;
        this.setPlayStatus(PlayerStatusEnum.UN_READY);
    }

    /**
     * 初始化
     */
    public void init() {
        // 当前状态
        this.playStatus = PlayerStatusEnum.UN_READY;
        // 用户当前拥有的牌
        this.ownedTiles = new ArrayList<>();
        // 用户吃、碰、杠等牌组
        this.ownedTilesGroup = new ArrayList<>();
        // 用户花牌牌组
        this.ownedHuaTilesGroup = new ArrayList<>();
        // 用户打出的牌
        this.playedTiles = new ArrayList<>();
    }

    public List<List<TilesEnum>> getHuaAnGangGroups() {
        List<List<TilesEnum>> result = new ArrayList<>();
        List<TilesEnum> heiHuaList = TilesEnum.getByType(CardTypeEnum.HEI_HUA);
        if (this.ownedTiles.containsAll(heiHuaList)) {
            result.add(heiHuaList);
        }
        List<TilesEnum> hongHuaList = TilesEnum.getByType(CardTypeEnum.HONG_HUA);
        if (this.ownedTiles.containsAll(hongHuaList)) {
            result.add(hongHuaList);
        }
        if (this.getOwnedThisTileCount(TilesEnum.BAI_BAN) == 4) {
            result.add(TilesEnum.buildGang(TilesEnum.BAI_BAN));
        }
        return result;
    }

    // 获取拥有的花牌
    public List<TilesEnum> getOwnedHuaTiles() {
        return this.ownedTiles.stream()
                .filter(tile -> tile.getType() == CardTypeEnum.HONG_HUA || tile.getType() == CardTypeEnum.HEI_HUA || tile.getType() == CardTypeEnum.BAI_BAN)
                .collect(Collectors.toList());
    }

    // 获取可以暗杠的手牌
    public List<TilesEnum> getAnGangTiles() {
        List<TilesEnum> result = new ArrayList<>();
        // 统计手上有没有牌可以凑齐四张的
        Map<TilesEnum, Long> countMap = this.ownedTiles.stream()
                .filter(tile -> tile.getType() != CardTypeEnum.BAI_BAN && tile.getType() !=CardTypeEnum.HONG_HUA || tile.getType() != CardTypeEnum.HEI_HUA)
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
        for(TilesEnum tile : countMap.keySet()) {
            if (countMap.get(tile).intValue() == 4) {
                result.add(tile);
            }
        }
        return result;
    }

    /**
     * 抓牌之后获取可以明杠的牌
     * @return
     */
    public List<TilesEnum> getMingGangTilesAfterGrabbing() {
        // 遍历所有碰牌牌组，判断手牌中是否剩余的另外一张
        List<TilesEnum> resultList = new ArrayList<>();
        ownedTilesGroup.stream().forEach(group -> {
            if (group.getType() == TilesGroupTypeEnum.PENG) {
                TilesEnum tile = group.getTiles().get(0);
                if (ownedTiles.contains(tile)) {
                    resultList.add(tile);
                }
            }
        });
        return resultList;
    }

    /**
     * 抓牌之后可以暗杠的手牌
     * @return
     */
    public List<TilesEnum> getAnGangTilesAfterGrabbing() {
        List<TilesEnum> resultList = new ArrayList<>();
        Map<TilesEnum, Long> countMap = this.ownedTiles.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
        for (TilesEnum tile: countMap.keySet()) {
            if (countMap.get(tile).intValue() == 4) {
                resultList.add(tile);
            }
        }
        return resultList;
    }

    public void setOwnedTiles(List<TilesEnum> ownedTiles) {
        this.ownedTiles = ownedTiles;
        sortOwnedTiles();
    }

    /**
     * 判断是否拥有这张手牌
     * @param tile
     * @return
     */
    public boolean hasOwnedTile(TilesEnum tile) {
        return this.ownedTiles.contains(tile);
    }

    /**
     * 获取这张手牌的数量
     * @param tile
     * @return
     */
    public int getOwnedThisTileCount(TilesEnum tile) {
        return (int) this.ownedTiles.stream().filter(_tile -> _tile == tile).count();
    }



    /**
     * 判断是否拥有这些手牌
     * @param tiles
     * @return
     */
    public boolean hasOwnedTiles(List<TilesEnum> tiles) {
        return this.ownedTiles.containsAll(tiles);
    }

    /**
     * 移除一张手牌
     * @param tile
     */
    public synchronized void removeOwnedTile(TilesEnum tile) {
        this.ownedTiles.remove(tile);
    }

    /**
     * 将某张牌全部移除出手牌
     * @param tile
     */
    public synchronized void removeOwnedAllThisTile(TilesEnum tile) {
        List<TilesEnum> tiles = new ArrayList<>();
        tiles.add(tile);
        this.ownedTiles.removeAll(tiles);
    }

    /**
     * 移除一张手牌
     * @param tiles
     */
    public synchronized void removeOwnedTiles(List<TilesEnum> tiles) {
        this.ownedTiles.removeAll(tiles);
    }

    /**
     * 多次移出某张手牌
     */
    public synchronized void removeOwnedTiles(TilesEnum tile, int count) {
        if (count <= 0) {
            return;
        }
        for (int i = 0; i<count; i++) {
            this.ownedTiles.remove(tile);
        }
    }

    /**
     * 移除最近一张用户打过的手牌
     */
    public synchronized void removeRecentPlayedTiles() {
        this.playedTiles.remove(playedTiles.size() - 1);
    }

    /**
     * 打牌
     * @param tile
     */
    @SneakyThrows
    public synchronized void playTheCard(TilesEnum tile) {
        if (!hasOwnedTile(tile)) {
            throw new CustomException("您手里没有这张牌，请勿当老六");
        }
        // 加入已打过的牌队列
        this.playedTiles.add(tile);
        // 从个人手牌中移除这张牌
        this.ownedTiles.remove(tile);
    }

    /**
     * 抓牌
     * @param tile
     */
    public synchronized void grabbing(TilesEnum tile) {
        this.ownedTiles.add(tile);
        sortOwnedTiles();
    }

    /**
     * 对手牌进行排序，首先按照万、条、筒等牌型的code进行正序排序，然后按照牌的code进行正序排序
     */
    private synchronized void sortOwnedTiles() {
        Room room = RoomManager.getUserRoom(userId);
        Dealers dealers = room.getDealers();
        // 拥有的百搭牌
        List<TilesEnum> ownedVersatileTiles = new ArrayList<>();
        // 除百搭牌外的其他牌
        List<TilesEnum> otherTiles = new ArrayList<>();
        this.ownedTiles.forEach(tile -> {
            if (dealers.isVersatile(tile)) {
                ownedVersatileTiles.add(tile);
            } else {
                otherTiles.add(tile);
            }
        });
        // 对百搭牌进行排序
        Collections.sort(ownedVersatileTiles, (o1, o2) -> {
            // 如果是同类型的牌，则对比牌code大小
            if (o1.getType() == o2.getType()) {
                return o1.getCode() - o2.getCode();
            }
            // 如果不是同类型的牌，则按照类型code排序
            else {
                return o1.getType().getCode() - o2.getType().getCode();
            }
        });
        // 对其他牌进行排序
        Collections.sort(otherTiles, (o1, o2) -> {
            // 如果是同类型的牌，则对比牌code大小
            if (o1.getType() == o2.getType()) {
                return o1.getCode() - o2.getCode();
            }
            // 如果不是同类型的牌，则按照类型code排序
            else {
                return o1.getType().getCode() - o2.getType().getCode();
            }
        });
        this.ownedTiles.clear();
        // 百搭牌放前边
        this.ownedTiles.addAll(ownedVersatileTiles);
        this.ownedTiles.addAll(otherTiles);
    }

    /**
     * 吃牌
     * @param it
     * @param selectedGroup
     */
    public synchronized List<TilesEnum> chi(TilesEnum it, List<TilesEnum> selectedGroup) {
        List<TilesEnum> tiles = new ArrayList<>();
        tiles.add(it);
        tiles.addAll(selectedGroup);
        // 按照code进行正序排序
        tiles = tiles.stream().sorted(Comparator.comparing(TilesEnum::getCode)).collect(Collectors.toList());
        // 加入吃牌牌组
        TilesGroup group = new TilesGroup(TilesGroupTypeEnum.CHI, tiles);
        this.ownedTilesGroup.add(group);
        return tiles;
    }

    /**
     * 碰牌
     * @param it
     */
    public synchronized void peng(TilesEnum it) {
        List<TilesEnum> tiles = new ArrayList<>();
        tiles.add(it);
        tiles.add(it);
        tiles.add(it);
        // 加入碰牌牌组
        TilesGroup group = new TilesGroup(TilesGroupTypeEnum.PENG, tiles);
        this.ownedTilesGroup.add(group);
    }


    /**
     * 明杠某张牌
     * @param tile
     */
    public synchronized void mingGangIt(TilesEnum tile) {
        TilesGroup group = new TilesGroup(TilesGroupTypeEnum.MING_GANG, TilesEnum.buildGang(tile));
        this.ownedTilesGroup.add(group);
    }

    /**
     * 抓牌之后明杠
     * @param it
     */
    public synchronized void mingGangItAfterGrabbing(TilesEnum it) {
        List<TilesEnum> gangTiles = new ArrayList<>();
        int index = 0;
        // 碰牌牌组变成明杠牌组
        for (int i=0; i<this.ownedTilesGroup.size();i++) {
            TilesGroup group = this.ownedTilesGroup.get(i);
            if (group.getType() == TilesGroupTypeEnum.PENG && group.getTiles().contains(it)) {
                gangTiles.addAll(group.getTiles());
                index = i;
            }
        }
        if (CollectionUtils.isEmpty(gangTiles)) {
            return;
        }
        gangTiles.add(it);
        TilesGroup group = new TilesGroup(TilesGroupTypeEnum.MING_GANG, gangTiles);
        this.ownedTilesGroup.set(index, group);
    }
    /**
     * 暗杠某张牌
     * @param tile
     */
    public synchronized void anGang(TilesEnum tile) {
        TilesGroup group = new TilesGroup(TilesGroupTypeEnum.AN_GANG, TilesEnum.buildGang(tile));
        this.ownedTilesGroup.add(group);
    }

    /**
     * 暗杠某种花
     * @param type
     */
    public synchronized void huaAnGang(CardTypeEnum type) {
        TilesGroup group = new TilesGroup(TilesGroupTypeEnum.AN_GANG, TilesEnum.getByType(type));
        this.ownedTilesGroup.add(group);
    }

    /**
     * 在牌组中获取拥有的某个类型的所有花牌
     * @return
     */
    public List<TilesEnum> getOwnedThisHuaTilesInGroup(TilesGroupTypeEnum groupType) {
        // 获取拥有的该类型的所有花牌
        List<TilesEnum> ownedHuaTiles = new ArrayList<>();
        for (TilesGroup group : this.ownedHuaTilesGroup) {
            if (group.getType() == groupType) {
                ownedHuaTiles.addAll(group.getTiles());
                break;
            }
        }
        return ownedHuaTiles;
    }

    /**
     * 获取某个牌组的全部牌
     * @return
     */
    public List<TilesGroup> getOwnedGroupByGroupType(TilesGroupTypeEnum groupType) {
        return ownedTilesGroup.stream().filter(group -> group.getType() == groupType).collect(Collectors.toList());
    }

    /**
     * 是否碰过某张牌
     * @param tile
     * @return
     */
    public boolean hasPeng(TilesEnum tile) {
        // 获取碰过的所有牌组
        List<TilesGroup> groupList = getOwnedGroupByGroupType(TilesGroupTypeEnum.PENG);
        if (CollectionUtils.isEmpty(groupList)) {
            return false;
        }
        // 查找这些碰过的牌组中是否有这张牌
        for (TilesGroup group : groupList) {
            if (group.getTiles().contains(tile)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 添加某张牌进花牌牌组
     * @param tile
     */
    public synchronized void addOwnedHuaTilesGroup(TilesEnum tile) {
        TilesGroupTypeEnum groupType;
        CardTypeEnum type = tile.getType();
        if (type == CardTypeEnum.HEI_HUA) {
            groupType = TilesGroupTypeEnum.HEI_HUA;
        } else if (type == CardTypeEnum.HONG_HUA) {
            groupType = TilesGroupTypeEnum.HONG_HUA;
        } else if (type == CardTypeEnum.BAI_BAN) {
            groupType = TilesGroupTypeEnum.BAI_BAN;
        } else {
            return;
        }
        // 查找对应的花牌组，没有则添加
        for (TilesGroup group : ownedHuaTilesGroup) {
            if (group.getType() == groupType) {
                group.addTile(tile);
                return;
            }
        }
        // 如果没有，则添加新的牌组
        List<TilesEnum> tiles = new ArrayList<>();
        tiles.add(tile);
        TilesGroup ownedHuaGroup = new TilesGroup(groupType, tiles);
        this.ownedHuaTilesGroup.add(ownedHuaGroup);
    }
}
